/*
 * @Author: xiaosihan
 * @Date: 2021-12-20 13:38:22
 * @Last Modified by: xiaosihan
 * @Last Modified time: 2025-01-28 04:26:41
 */

import { round } from "lodash";
import {
    AxesHelper,
    Box3,
    DepthFormat,
    DepthTexture,
    EventDispatcher,
    GridHelper,
    LinearFilter,
    MOUSE,
    NoBlending,
    NormalBlending,
    Object3D,
    OrthographicCamera,
    PerspectiveCamera,
    RGBAFormat,
    ReinhardToneMapping,
    SRGBColorSpace,
    Scene,
    TOUCH,
    Texture,
    UnsignedShortType,
    Vector2,
    Vector3,
    WebGLRenderTarget,
    WebGLRenderer,
} from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { degToRad, generateUUID } from "three/src/math/MathUtils";
import ThreeMouse from "./ThreeMouse";
import Transition from "./Transition";
import bloom_pass from "./pass/bloom_pass";
import cover_pass from "./pass/cover_pass";
import glow_pass from "./pass/glow_pass";
import stroke_pass from "./pass/stroke_pass";
import { copyShader } from "./shaders";
import { baseTextureToRenderShader } from "./shaders/baseTextureToRenderShader";
import threeLoader from "./threeLoader";
import threeUtils from "./threeUtils";
import fsQuad from "./pass/fsQuad";

// three.js 的基础对象
export default class ThreeBase extends EventDispatcher {
    constructor() {
        super();

        // 资源加载进度有更新时, 需要渲染
        threeLoader.addEventListener("loadding", () => {
            this.dispatchEvent({ type: "loadding" } as never);
            setTimeout(() => {
                this.needRender = true;
            }, 100);
        });

        threeLoader.addEventListener("onLoad", () => {
            this.dispatchEvent({ type: "onLoad" } as never);
            setTimeout(() => {
                this.needRender = true;
            }, 100);
        });

        this.init();
    }

    // 设置最小分辨率
    minDevicePixelRatio = 1;


    // 基础深度纹理
    baseDepthTexture = (() => {
        const baseDepthTexture = new DepthTexture(4096, 4096);
        baseDepthTexture.format = DepthFormat;
        baseDepthTexture.type = UnsignedShortType;
        baseDepthTexture.anisotropy = 16;
        return baseDepthTexture;
    })();

    // 基础纹理渲染对象
    baseRenderTarget = (() => {
        const baseRenderTarget = new WebGLRenderTarget(128, 128, {
            minFilter: LinearFilter,
            magFilter: LinearFilter,
            format: RGBAFormat,
            stencilBuffer: false,
            depthTexture: this.baseDepthTexture,
            anisotropy: 16,
            colorSpace: SRGBColorSpace,
        });
        return baseRenderTarget;
    })();

    // 公用渲染目标1
    renderTarget = new WebGLRenderTarget(128, 128, {
        minFilter: LinearFilter,
        magFilter: LinearFilter,
        format: RGBAFormat,
    });

    // 渲染目标2
    renderTarget2 = new WebGLRenderTarget(128, 128, {
        minFilter: LinearFilter,
        magFilter: LinearFilter,
        format: RGBAFormat,
    });

    // 公用二维向量
    tempVector2 = new Vector2();

    // 公用的三维向量
    tempVector3 = new Vector3();

    //渲染器的尺寸
    size = new Vector2(0, 0);

    //上下文丢失
    rendererLost = false;

    // 渲染器对象
    renderer = (() => {
        const renderer = new WebGLRenderer({
            alpha: true, // 透明背景
            antialias: true, // 抗锯齿
            precision: "highp", //highp/mediump/lowp着色精度选择
            logarithmicDepthBuffer: true, // 对数深度缓冲区 避免当2个面挨的很近时闪烁
            preserveDrawingBuffer: true, // 是否保存绘图缓冲
            stencil: false, // 模版
            powerPreference: 'high-performance' // 高性能模式
        });

        renderer.domElement.id = generateUUID();
        //监听上下文的销毁事件
        renderer.domElement.addEventListener(
            "webglcontextlost",
            (event) => {
                this.rendererLost = true;
            },
            false
        );
        // 监听上下文的恢复事件
        renderer.domElement.addEventListener(
            "webglcontextrestored",
            (event) => {
                this.rendererLost = false;
            },
            false
        );
        renderer.localClippingEnabled = true;
        renderer.setPixelRatio(Math.max(window.devicePixelRatio, this.minDevicePixelRatio)),
            renderer.autoClear = false;
        renderer.clear();
        renderer.outputColorSpace = SRGBColorSpace;
        // 缓解过暗或者过亮
        renderer.toneMapping = ReinhardToneMapping;
        threeLoader.setAnisotropy(renderer.capabilities.getMaxAnisotropy());
        return renderer;
    })();

    //透视相机
    perspectiveCamera = new PerspectiveCamera(45, 1, 0.01, 20000);
    //正交相机
    orthographicCamera = new OrthographicCamera(-1, 1, 1, -1, 0.01, 20000);

    //相机的类型 有透视相机和正交相机
    cameraType: "perspectiveCamera" | "orthographicCamera" = "perspectiveCamera";
    setCameraType(cameraType: "perspectiveCamera" | "orthographicCamera") {
        if (this.cameraType !== cameraType) {
            this.cameraType = cameraType;
            if (this.cameraType === "perspectiveCamera") {
                this.threeMouse._camera = this.perspectiveCamera;
                this.controls.object = this.perspectiveCamera;
            } else if (this.cameraType === "orthographicCamera") {
                this.threeMouse._camera = this.orthographicCamera;
                this.controls.object = this.orthographicCamera;
            }
        }
    }
    //获取当前使用的的相机
    getCamera() {
        switch (this.cameraType) {
            case "orthographicCamera":
                return this.orthographicCamera;
            case "perspectiveCamera":
            default:
                return this.perspectiveCamera;
        }
    }

    // 场景对象
    scene = new Scene();

    // 这个方法等效 this.scene.add
    // 想少写2个字母就用这个方法
    add(...object: Object3D[]) {
        this.scene.add(...object);
        return this;
    }

    ///鼠标事件对象
    threeMouse = new ThreeMouse(
        this.renderer,
        this.perspectiveCamera,
        this.scene
    );

    // 轨道控制器
    controls = (() => {
        const controls = new OrbitControls(
            this.perspectiveCamera,
            this.renderer.domElement
        );
        controls.addEventListener("change", () => {
            this.needRender = true;
        });
        controls.rotateSpeed = 0.2;
        controls.maxPolarAngle = Math.PI;
        controls.enableDamping = true;
        controls.dampingFactor = 0.1;
        controls.screenSpacePanning = true;
        return controls;
    })();

    // 设置 dom 容器
    async setContainer(div: HTMLDivElement | null) {
        if (div) {
            div.style.overflow = "hidden";
            div.innerHTML = "";
            div.append(this.renderer.domElement);

            if (this.rendererLost) {
                this.renderer.forceContextRestore();
                this.needRender = true;
                setTimeout(() => {
                    this.needRender = true;
                }, 100);
                setTimeout(() => {
                    this.renderer.domElement.style.opacity = "1";
                }, 200);
            }

            this.setSize(div.clientWidth, div.clientHeight);

            // 开启时时渲染
            threeUtils.removeEventListener(
                "requestAnimationFrame",
                this.renderBindThis
            );
            threeUtils.addEventListener("requestAnimationFrame", this.renderBindThis);
        }
    }

    // 修改尺寸
    setSize(width: number, height: number) {
        //修改透视相机的比例
        this.perspectiveCamera.aspect = width / height;
        this.perspectiveCamera.updateProjectionMatrix();

        //修改正交相机的比例
        // this.orthographicCamera.top = height / width;
        // this.orthographicCamera.bottom = -height / width;
        this.orthographicCamera.left = -width / height;
        this.orthographicCamera.right = width / height;

        this.renderer.setPixelRatio(Math.max(window.devicePixelRatio, this.minDevicePixelRatio)),
            this.renderer.setSize(width, height);

        // 设置帧缓存区的尺寸
        this.baseRenderTarget.setSize(
            width * Math.max(window.devicePixelRatio, this.minDevicePixelRatio),
            height * Math.max(window.devicePixelRatio, this.minDevicePixelRatio),
        );

        // 设置深度纹理的尺寸
        this.baseDepthTexture.image.width = width * Math.max(window.devicePixelRatio, this.minDevicePixelRatio),
            this.baseDepthTexture.image.height = height * Math.max(window.devicePixelRatio, this.minDevicePixelRatio),

            this.size.set(width, height);

        setTimeout(() => {
            this.needRender = true;
        }, 100);
        // 渲染场景
        // this.renderer.clear();
        // this.renderer.render(this.scene, this.camera);
    }

    // 视角距离过度
    controlsDistance = (() => {
        const controlsDistance = new Transition({ distance: 0 });
        controlsDistance.setDuration(1000);
        return controlsDistance;
    })();

    // 控制器的 朝向带缓动的计算的顶点对象
    controlsCenter = (() => {
        const controlsCenter = new Transition({ x: 0, y: 0, z: 0 });
        controlsCenter.setDuration(1000);
        controlsCenter.onChange(({ x, y, z }) => {
            this.controls.target.set(x, y, z);
            this.perspectiveCamera.lookAt(this.controls.target);
            this.orthographicCamera.lookAt(this.controls.target);
            this.needRender = true;
        });
        return controlsCenter;
    })();

    // 控制器的位置缓动的计算的顶点对象
    controlsPosition = (() => {
        const controlsPosition = new Transition({ x: 100, y: 100, z: 100 });
        controlsPosition.setDuration(1000);
        controlsPosition.onChange(({ x, y, z }) => {
            // const distance = this.controlsDistance.get("distance");
            this.perspectiveCamera.position.set(x + 0.01, y, z + 0.01);
            // .sub(this.controls.target)
            // .setLength(distance)
            // .add(this.controls.target);

            // // 创建初始方向向量
            // const directionVecA = new Vector3(1, 0, 0); // 需要旋转的初始方向向量
            // const referenceVecB = new Vector3(0, 1, 0); // 参考方向向量，用于确定旋转轴

            // // 归一化参考方向向量
            // referenceVecB.normalize();

            // // 计算旋转轴
            // const axis = new Vector3().crossVectors(directionVecA, referenceVecB).normalize();

            // // 计算旋转角度（弧度）
            // const angle = Math.PI / 36; // 旋转角度为5度，Math.PI / 36为弧度

            // // 使用applyAxisAngle方法进行旋转
            // directionVecA.applyAxisAngle(axis, angle);

            // // 输出旋转后的方向向量
            // console.log(directionVecA);

            this.perspectiveCamera.lookAt(this.controls.target);

            //计算正交相机的缓动
            this.orthographicCamera.position
                .set(x + 0.01, y, z + 0.01)
                .sub(this.controls.target)
                .setLength(5000)
                .add(this.controls.target);
            this.orthographicCamera.lookAt(this.controls.target);

            this.needRender = true;
        });
        return controlsPosition;
    })();

    // 网格辅助线
    gridHelper = (() => {
        const gridHelper = new GridHelper(1000, 100, 0x303030, 0x303030);
        if (threeUtils.isDev) {
            this.scene.add(gridHelper);
        }
        return gridHelper;
    })();

    //坐标轴线
    axesHelper = (() => {
        const axesHelper = new AxesHelper(10000);
        if (threeUtils.isDev) {
            this.scene.add(axesHelper);
        }
        return axesHelper;
    })();

    //TODO 设置帧频率 默认50帧
    frame = 30;

    init() { }

    // 重置控制器的位置
    resetLookAt({
        position = this.perspectiveCamera.position,
        center = this.controls.target,
    }: {
        position: Vector3;
        center: Vector3;
    }) {
        // 停止控制器的惯性
        const originEnableDamping = this.controls.enableDamping;
        this.controls.saveState();
        this.controls.update();
        this.controls.reset();
        this.controls.enableDamping = originEnableDamping;

        this.controlsDistance.reset({
            distance: new Vector3(position.x, position.y, position.z).distanceTo(
                new Vector3(center.x, center.y, center.z)
            ),
        });
        position &&
            this.controlsPosition.reset({
                x: position.x,
                y: position.y,
                z: position.z,
            });
        center &&
            this.controlsCenter.reset({ x: center.x, y: center.y, z: center.z });
        this.needRender = true;
    }

    // 设置控制器的位置 以动画的方式移动控制器
    async setLookAt({
        position = this.perspectiveCamera.position,
        center = this.controls.target,
        duration = 1000,
    }: {
        position: Vector3;
        center: Vector3;
        duration?: number;
    }) {
        // 停止控制器的惯性
        const originEnableDamping = this.controls.enableDamping;
        this.controls.saveState();
        this.controls.update();
        this.controls.reset();
        this.controls.enableDamping = originEnableDamping;

        let currentPosition = this.perspectiveCamera.position.clone();
        let currentCenter = this.controls.target.clone();

        // 等待动画执行完成
        await Promise.all([
            new Promise<void>(async (resolve, reject) => {
                this.controlsDistance.setDuration(duration).reset({
                    distance: this.perspectiveCamera.position.distanceTo(
                        this.controls.target
                    ),
                });
                await this.controlsDistance.set({
                    distance: new Vector3(position.x, position.y, position.z).distanceTo(
                        new Vector3(center.x, center.y, center.z)
                    ),
                });
                resolve();
            }),
            new Promise<void>(async (resolve, reject) => {
                this.controlsPosition.setDuration(duration).reset({
                    x: currentPosition.x,
                    y: currentPosition.y,
                    z: currentPosition.z,
                });
                await this.controlsPosition.set({
                    x: position.x,
                    y: position.y,
                    z: position.z,
                });
                resolve();
            }),
            new Promise<void>(async (resolve, reject) => {
                this.controlsCenter.setDuration(duration).reset({
                    x: currentCenter.x,
                    y: currentCenter.y,
                    z: currentCenter.z,
                });
                await this.controlsCenter.set({
                    x: center.x,
                    y: center.y,
                    z: center.z,
                });
                resolve();
            }),
        ]);
    }

    // 获取控制的位置和朝向 保留3位小数
    getLookAt() {
        //判断需要使用那个相机
        const position = this.perspectiveCamera.position
            .toArray()
            .map((v) => round(v, 3));
        const center = this.controls.target.toArray().map((v) => round(v, 3));
        return {
            center: new Vector3(...center),
            position: new Vector3(...position),
            centerPosition: `center: new Vector3(${center.join(
                ","
            )}),position: new Vector3(${position.join(",")})`,
        };
    }

    // 查看对象
    lookatObjects({
        objects,
        duration = 1000,
        direction = new Vector3(0.3, 0.3, 1),
        offset = new Vector3(0, 0, 0),
        distance = 1,
    }: {
        objects: Array<Object3D>;
        duration?: number;
        direction?: Vector3;
        offset?: Vector3;
        distance?: number;
    }) {
        const box3 = new Box3();
        objects.map((o) => {
            o.traverseVisible((oo) => {
                box3.expandByObject(oo);
            });
        });
        const size = new Vector3();
        box3.getSize(size);
        const center = new Vector3();
        box3.getCenter(center);

        center.add(offset);
        const modelSize = size.length() * distance;
        if (this.cameraType === "perspectiveCamera") {
            const { fov } = this.getCamera() as PerspectiveCamera;
            const cameraDistance = modelSize / (2 * Math.tan((fov * Math.PI) / 360));
            const position = direction.setLength(cameraDistance).add(center);
            this.setLookAt({ center, position, duration });
        }
    }

    // 三维向量转化成屏幕上的2维向量
    v3tov2(v3: Vector3) {
        //判断需要使用那个相机
        const camera = this.getCamera();
        v3.project(camera);
        const v2 = new Vector2(
            (v3.x + 1) * this.size.x * 0.5,
            (-v3.y + 1) * this.size.y * 0.5
        );
        return v2;
    }

    // 控制器类型
    controlType: "OrbitControls" | "MapControls" = "OrbitControls";

    // 记录一下空间平移方式 在切换控制器的时候保留一下平移方式
    originScreenSpacePanning = false;

    //改变控制器的类型
    setControlType(controlType: "OrbitControls" | "MapControls") {
        if (controlType === "OrbitControls") {
            this.controls.screenSpacePanning = this.originScreenSpacePanning;
            this.controls.mouseButtons = {
                LEFT: MOUSE.ROTATE,
                MIDDLE: MOUSE.DOLLY,
                RIGHT: MOUSE.PAN,
            };
            this.controls.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };
        } else if (controlType === "MapControls") {
            this.originScreenSpacePanning = this.controls.screenSpacePanning;
            // pan orthogonal to world-space direction camera.up
            this.controls.screenSpacePanning = false;
            this.controls.mouseButtons.LEFT = MOUSE.PAN;
            this.controls.mouseButtons.RIGHT = MOUSE.ROTATE;
            this.controls.touches.ONE = TOUCH.PAN;
            this.controls.touches.TWO = TOUCH.DOLLY_ROTATE;
        }
    }

    // 是否自动渲染
    autoRender = true;

    //是否需要渲染, 当autoRender=false的时候就需要 手动触发渲染
    needRender = false;

    // 停止渲染
    stopRender() {
        threeUtils.removeEventListener(
            "requestAnimationFrame",
            this.renderBindThis
        );
        if (!this.rendererLost) {
            this.renderer.forceContextLoss();
            this.setSize(100, 100);
            this.renderer.domElement.style.opacity = "0";
        }
    }

    // 渲染场景
    render() {
        // canvas 没有在页面上面的话 就停止渲染
        if (!threeUtils.inDocument(this.renderer.domElement)) {
            this.stopRender();
            return;
        }

        // 相机的朝向
        const center = new Vector3(
            ...this.controls.target.toArray().map((v) => round(v, 3))
        );

        //透视相机和正交相机参数的时时转换
        if (this.cameraType === "perspectiveCamera") {
            const { fov } = this.perspectiveCamera;
            const distaned = this.perspectiveCamera.position
                .clone()
                .sub(center)
                .length();
            const zoom = 1 / (Math.tan(degToRad(fov) / 2) * distaned);
            this.orthographicCamera.zoom = zoom;
            this.orthographicCamera.position
                .copy(this.perspectiveCamera.position.clone().sub(center))
                .setLength(2500)
                .add(center);
        } else if (this.cameraType === "orthographicCamera") {
            const { fov } = this.perspectiveCamera;
            const { zoom } = this.orthographicCamera;
            const distaned = 1 / zoom / Math.tan(degToRad(fov) / 2);
            this.perspectiveCamera.position
                .copy(this.orthographicCamera.position.clone().sub(center))
                .setLength(distaned)
                .add(center);
        }

        //判断需要使用那个相机
        const camera = this.getCamera();

        //判断尺寸发生改变时重置尺寸
        const parentNode = this.renderer.domElement.parentNode as HTMLDivElement;
        if (
            parentNode.clientWidth !== this.size.x ||
            parentNode.clientHeight !== this.size.y
        ) {
            this.setSize(parentNode.clientWidth, parentNode.clientHeight);
        }

        // 分发鼠标事件
        this.threeMouse.dispatchEvent();

        // 在不自动渲染时, needRender=false 就跳过渲染
        if (!this.autoRender && !this.needRender) {
            return; // 标识未渲染
        }
        this.needRender = false;

        if (this.controls.enabled) {
            this.controls.update();
        }

        // 渲染场景
        this.dispatchEvent({ type: "beforeRender" } as never);

        // 把基础场景渲染到 帧缓冲区 在后期通道中会用到
        this.renderer.setRenderTarget(this.baseRenderTarget);
        this.renderer.clear();
        this.renderer.render(this.scene, camera);

        // 后处理特效渲染(描边,发光,颜色覆盖,bloom,磨砂玻璃)
        this.scene.traverseVisible((obj) => {
            stroke_pass(
                this.renderer,
                camera,
                obj,
                this.baseDepthTexture,
                Math.max(window.devicePixelRatio, this.minDevicePixelRatio),
            );
            glow_pass(
                this.renderer,
                camera,
                obj,
                this.baseDepthTexture,
                Math.max(window.devicePixelRatio, this.minDevicePixelRatio),
            );
            bloom_pass(
                this.renderer,
                camera,
                obj,
                this.baseRenderTarget,
                this.baseDepthTexture,
                Math.max(window.devicePixelRatio, this.minDevicePixelRatio),
            );
        });

        this.renderer.setRenderTarget(null);
        this.renderer.clear();
        this.renderRendererTarget(this.baseRenderTarget);

        this.scene.traverseVisible((obj) => {
            cover_pass(
                this.renderer,
                camera,
                obj,
                this.baseDepthTexture,
                Math.max(window.devicePixelRatio, this.minDevicePixelRatio),
            );
        });

        this.dispatchEvent({ type: "afterRender" } as never);
    }

    private renderBindThis = this.render.bind(this);

    //获取渲染图blob对象
    async getThumbBlob() {
        this.renderer.setRenderTarget(this.baseRenderTarget);
        this.renderer.clear();
        this.renderer.render(this.scene, this.getCamera());
        this.renderer.setRenderTarget(null);
        this.renderer.clear();
        this.renderRendererTarget(this.baseRenderTarget);
        const blob = await new Promise<Blob>((resolve, reject) => {
            this.renderer.domElement.toBlob(
                (blob) => {
                    resolve(blob as Blob);
                },
                "image/png",
                1
            );
        });
        return blob;
    }
    //获取渲染图base64
    getThumbBase64() {
        this.renderer.setRenderTarget(this.baseRenderTarget);
        this.renderer.clear();
        this.renderer.render(this.scene, this.getCamera());
        this.renderer.setRenderTarget(null);
        this.renderer.clear();
        this.renderRendererTarget(this.baseRenderTarget);
        const base64 = this.renderer.domElement.toDataURL("image/png");
        return base64;
    }
    //获取渲染图文件对象
    async getThumbFile() {
        const blob = await this.getThumbBlob();
        const file = new File([blob], "thumb.png", { type: blob.type });
        return file;
    }

    // 下载渲染图
    async download(fileName: string = "download") {
        const blob = await this.getThumbBlob();
        let saveLink = document.createElement("a");
        saveLink.download = `${fileName}.png`;
        saveLink.href = URL.createObjectURL(blob);
        document.body.append(saveLink);
        saveLink.click();
    }

    //把渲染目标渲染到canvas上
    renderRendererTarget(renderTarget: WebGLRenderTarget) {
        baseTextureToRenderShader.uniforms.resolution.value.set(
            1 / this.size.x,
            1 / this.size.y
        );
        baseTextureToRenderShader.uniforms.tDiffuse.value = renderTarget.texture;
        fsQuad.render(this.renderer, baseTextureToRenderShader);
    }

    // 渲染纹理
    renderTexture(texture: Texture, alpha: boolean = false) {
        copyShader.uniforms.map.value = texture;
        copyShader.uniforms.opacity.value = 1.0;
        copyShader.blending = alpha ? NoBlending : NormalBlending;
        fsQuad.render(this.renderer, copyShader);
    }
}
